package module

import (
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/astaxie/beego/httplib"
	"github.com/hashicorp/consul/api"
)

type Client struct {
	consul             *api.Client
	consulAgent        *api.Agent
	consulHealth       *api.Health
	consulQueryOptions *api.QueryOptions
	serviceId          string
}

// 微服务配置实体
type serviceInfo struct {
	Type        string
	Name        string
	HttpAddress string
	RpcAddress  string
}

// 创建一个ModuleClient服务对象
func NewClient(server, port string) (*Client, error) {
	var err error

	if len(server) == 0 || len(port) == 0 {
		return nil, errors.New("参数不能为空字符串")
	}

	c := &Client{}

	config := api.DefaultConfig()
	config.Address = server + ":" + port

	c.consul, err = api.NewClient(config)
	if err != nil {
		return nil, err
	}

	c.consulAgent = c.consul.Agent()
	c.consulHealth = c.consul.Health()

	c.consulQueryOptions = &api.QueryOptions{WaitTime: 5 * time.Second}

	return c, nil
}

// 注册服务
func (c *Client) Register(name, server, httpPort, rpcPort, interval string) (err error) {
	if server == "localhost" || server == "127.0.0.1" {
		err = errors.New("不能使用本地地址进行服务注册")
	} else {
		c.serviceId = "go-" + name + "-id-" + server + "-" + httpPort + "-" + rpcPort
		port_num, _ := strconv.Atoi(httpPort)

		reg := &api.AgentServiceRegistration{
			ID:      c.serviceId,
			Name:    strings.Replace(c.serviceId, "-id-", "-name-", -1),
			Address: server,
			Port:    port_num,
			Check: &api.AgentServiceCheck{
				HTTP:     "http://" + server + ":" + httpPort + "/?action=heartbeat",
				Interval: interval,
				//TTL:      "15s",
				//Timeout: "30s",
			},
		}

		err = c.consulAgent.ServiceRegister(reg)
	}

	return
}

// 取消注册
func (c *Client) DeRegister() (err error) {
	if len(c.serviceId) > 0 {
		err = c.consulAgent.ServiceDeregister(c.serviceId)
		if err != nil {
			err = errors.New(c.serviceId + " 服务反注册失败：" + err.Error())
		}
	}

	return
}

func (c *Client) Get(reqInfo map[string]string) ([]byte, error) {
	return c.Request(reqInfo, "GET")
}

func (c *Client) Post(reqInfo map[string]string) ([]byte, error) {
	return c.Request(reqInfo, "POST")
}

// 请求服务方法
func (c *Client) Request(reqInfo map[string]string, method string) (res []byte, err error) {
	if reqInfo == nil {
		err = errors.New("Param不能为空")
		return
	}

	action, ok := reqInfo["action"]
	if !ok || len(action) <= 0 {
		err = errors.New("Action不能为空")
		return
	}

	actions := strings.Split(action, ".")

	// 兼容Gateway转发固定前缀、兼容V4接口内部请求
	moduleName := strings.ToLower(actions[0])
	switch moduleName {
	case "v3":
		moduleName = "apiv3"
		v3Index := len(actions[0]) + 1
		reqInfo["action"] = action[v3Index:]
	case "v4":
		moduleName = actions[1]
		v4Index := len(actions[0]) + 1
		reqInfo["action"] = action[v4Index:]
	}

	svrInfo, err := c.getServiceInfo(moduleName)
	if err == nil {
		method = strings.ToUpper(method)
		if method != "GET" {
			method = "POST"
		}
		req := httplib.NewBeegoRequest(svrInfo.HttpAddress, method)
		req.SetTimeout(5*time.Second, 5*time.Second)

		for k, v := range reqInfo {
			req.Param(k, v)
		}

		res, err = req.Bytes()
	}

	return
}

// 获取服务的RPC地址
func (c *Client) RpcAddress(moduleName string) (string, error) {
	svrInfo, err := c.getServiceInfo(moduleName)
	if err != nil {
		return "", err
	}

	return svrInfo.RpcAddress, nil
}

// 获得可用服务信息
func (c *Client) getServiceInfo(moduleName string) (*serviceInfo, error) {
	serviceList, _, err := c.consulHealth.State("passing", c.consulQueryOptions)
	if err != nil {
		return nil, errors.New(moduleName + "服务检查健康实例出错：" + err.Error())
	}

	serviceInfos := make([]serviceInfo, 0, 10)
	for _, s := range serviceList {
		// [0:类型] [1:模块名称] [2:consule内部] [3:地址] [4:http端口] [5:rpc端口]
		service := strings.Split(s.ServiceName, "-")

		//长度正确
		if len(service) == 6 {
			//取出匹配的服务信息
			if strings.EqualFold(service[1], moduleName) {
				svrInfo := serviceInfo{
					Type: strings.ToLower(service[0]),
				}

				switch svrInfo.Type {
				case "go":
					svrInfo.HttpAddress = fmt.Sprintf("http://%s:%s", service[3], service[4])
					svrInfo.RpcAddress = fmt.Sprintf("ws://%s:%s", service[3], service[5])
				case "dotnet":
					svrInfo.HttpAddress = fmt.Sprintf("http://%s:%s/%s", service[3], service[4], "v3handler.ashx")
					svrInfo.RpcAddress = fmt.Sprintf("http://%s:%s/%s", service[3], service[5], "rpchandler.ashx")
				}

				if len(svrInfo.HttpAddress) > 0 {
					svrInfo.Name = moduleName
					serviceInfos = append(serviceInfos, svrInfo)
				}
			}
		}
	}

	serviceLength := len(serviceInfos)
	if serviceLength == 0 {
		return nil, errors.New(moduleName + "服务没有健康的实例")
	}

	t := time.Now().Unix()
	i := t % int64(serviceLength)

	return &serviceInfos[i], nil
}
